package com.dd.cloud.user.service.sms;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.dd.cloud.common.config.sms.ManageSmsProperties;
import com.dd.cloud.common.constants.SmsConstants;
import com.dd.cloud.common.utils.JsonUtils;
import com.dd.cloud.common.utils.StringOperUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author zk
 */
@Slf4j
@Component
@EnableConfigurationProperties(ManageSmsProperties.class)
public class SmsUtils {
    @Autowired
    private ManageSmsProperties prop;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
     */
    private static final String WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
    /**
     * 无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
     */
    private static final String AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
    /**
     * 短信redis键值
     */
    private final static String KEY_PREFIX = "zkop:sms:phone:";
    /**
     * 短信60秒限流
     */
    private final static long SMS_MIN_INTERVAL_IN_MILLIS = 60000;
    /**
     * 产品名称:云通信短信API产品,开发者无需替换
     */
    private static final String PRODUCT = "Dysmsapi";
    /**
     * 产品域名,开发者无需替换
     */
    private static final String DOMAIN = "dysmsapi.aliyuncs.com";

    /**
     * 1华为云 2阿里云
     */
    @Value("${dadao.sms.flag}")
    private int flag;


    /**
     * 构造请求Body体
     *
     * @param sender         国内短信签名通道号或国际/港澳台短信通道号
     * @param receiver       接收人号码 多个,隔开
     * @param templateId     模板id
     * @param templateParas  模板变量
     * @param statusCallBack 选填,短信状态报告接收地址
     * @param signature      签名名称,使用国内短信通用模板时填写
     * @return
     */
    public static String buildRequestBody(String sender, String receiver, String templateId, String templateParas, String statusCallBack, String signature) {
        if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() || templateId.isEmpty()) {
            System.out.println("buildRequestBody(): sender, receiver or templateId is null.");
            return null;
        }
        Map<String, String> map = new HashMap<>(6);
        map.put("from", sender);
        map.put("to", receiver);
        map.put("templateId", templateId);
        if (null != templateParas && !templateParas.isEmpty()) {
            map.put("templateParas", templateParas);
        }
        if (null != statusCallBack && !statusCallBack.isEmpty()) {
            map.put("statusCallback", statusCallBack);
        }
        if (null != signature && !signature.isEmpty()) {
            map.put("signature", signature);
        }
        StringBuilder sb = new StringBuilder();
        String temp = "";
        for (String s : map.keySet()) {
            try {
                temp = URLEncoder.encode(map.get(s), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            sb.append(s).append("=").append(temp).append("&");
        }
        return sb.deleteCharAt(sb.length() - 1).toString();
    }

    /**
     * 短信模板变量数据处理
     *
     * @param map 模板数据
     * @return
     */
    public String mapToTemplateParas(Map<String, String> map) {
        String paramType = map.get("param_type");
        map.remove("param_type");
        if (SmsConstants.ParamType.JSON.equals(paramType)) {
            return JsonUtils.serialize(map);
        } else if (SmsConstants.ParamType.JSON_ARRAY.equals(paramType)) {
            StringBuilder sb = new StringBuilder();
            sb.append("[\"");
            for (int i = 1; i <= map.size(); i++) {
                sb.append(map.get(String.valueOf(i))).append("\",\"");
            }
            sb.delete(sb.length() - 2, sb.length());
            sb.append("]");
            return sb.toString();
        }
        return "";
    }

    public void sendHuaweiSms(HuaweiSmsProperties prop, String phone, String templateParas) {
        HostnameVerifier hv = (hostname, session) -> {
            String domain = StringOperUtil.extractDomain(prop.getUrl());
            return domain.equals(hostname);
        };
        try {

            trustAllHttpsCertificates();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //请求Headers中的X-WSSE参数值
        String wsseHeader = buildWsseHeader(prop.getAccessKeyId(), prop.getAccessKeySecret());
        if (null == wsseHeader || wsseHeader.isEmpty()) {
            System.out.println("wsse header is null.");
            return;
        }
        //请求Body,不携带签名名称时,signature请填null
        String body = buildRequestBody(prop.getSender(), phone, prop.getTemplate(), templateParas, "", prop.getSignName());
        if (null == body || body.isEmpty()) {
            System.out.println("body is null.");
            return;
        }
        Writer out = null;
        InputStream is = null;
        BufferedReader in = null;
        try {
            URL url = new URL(prop.getUrl());
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            connection.setHostnameVerifier(hv);
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(true);
            //请求方法
            connection.setRequestMethod("POST");
            //请求Headers参数
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            connection.setRequestProperty("Authorization", AUTH_HEADER_VALUE);
            connection.setRequestProperty("X-WSSE", wsseHeader);
            connection.connect();
            out = new OutputStreamWriter(connection.getOutputStream());
            out.write(body);
            out.flush();
            out.close();
            int status = connection.getResponseCode();
            if (200 == status) {
                is = connection.getInputStream();
            } else {
                is = connection.getErrorStream();
            }
            in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
            String line;
            StringBuffer result = new StringBuffer();
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("[短信服务] 响应结果 result:{}", result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != out) {
                    out.close();
                }
                if (null != is) {
                    is.close();
                }
                if (null != in) {
                    in.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static class SmsProperties {
        /**
         * app_key
         */
        private String accessKeyId;
        /**
         * app_secret
         */
        private String accessKeySecret;
        /**
         * 公司签名
         */
        private String signName;

        public SmsProperties(ManageSmsProperties prop) {
            if (prop == null) {
                return;
            }
            this.accessKeyId = prop.getAccessKeyId();
            this.accessKeySecret = prop.getAccessKeySecret();
            this.signName = prop.getSignName();
        }

        public String getAccessKeyId() {
            return accessKeyId;
        }

        public void setAccessKeyId(String accessKeyId) {
            this.accessKeyId = accessKeyId;
        }

        public String getAccessKeySecret() {
            return accessKeySecret;
        }

        public void setAccessKeySecret(String accessKeySecret) {
            this.accessKeySecret = accessKeySecret;
        }

        public String getSignName() {
            return signName;
        }

        public void setSignName(String signName) {
            this.signName = signName;
        }
    }

    public static void trustAllHttpsCertificates() throws Exception {
        TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }};
        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }

    public void sendSms(String phoneNumber, Map<String, String> msg) {
        String smsTemplate = msg.get("sms_template");
        msg.remove("sms_template");
        String key = KEY_PREFIX + smsTemplate + ":" + phoneNumber;
        // 对手机号码发送频率进行限流
        String lastTime = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotBlank(lastTime)) {
            String[] temp = lastTime.split("-");
            if (temp.length == 2) {
                long last = Long.parseLong(temp[0]);
                int num = Integer.parseInt(temp[1]);
                if (System.currentTimeMillis() - last < SMS_MIN_INTERVAL_IN_MILLIS && num >= 3) {
                    log.info("[短信服务] 发送短信失败,原因：频率过高，被拦截！ phoneNumber:{}", phoneNumber);
                    return;
                }
                num = num + 1;
                redisTemplate.opsForValue().set(key, System.currentTimeMillis() + "-" + num, 1L, TimeUnit.MINUTES);
            }
        } else {
            redisTemplate.opsForValue().set(key, System.currentTimeMillis() + "-1", 1L, TimeUnit.MINUTES);
        }

        try {
            switch (flag) {
                case 1:
                    HuaweiSmsProperties huaweiSmsProperties = new HuaweiSmsProperties(prop, smsTemplate);
                    sendHuaweiSms(huaweiSmsProperties, phoneNumber, mapToTemplateParas(msg));
                    break;
                case 2:
                    AliSmsProperties aliSmsProperties = new AliSmsProperties(prop, smsTemplate);
                    //可自助调整超时时间
                    System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
                    System.setProperty("sun.net.client.defaultReadTimeout", "10000");
                    //初始化acsClient,暂不支持region化
                    IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", prop.getAccessKeyId(), prop.getAccessKeySecret());
                    DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", PRODUCT, DOMAIN);
                    IAcsClient acsClient = new DefaultAcsClient(profile);
                    //组装请求对象-具体描述见控制台-文档部分内容
                    SendSmsRequest request = new SendSmsRequest();
                    request.setMethod(MethodType.POST);
                    //必填:待发送手机号
                    request.setPhoneNumbers(phoneNumber);
                    //必填:短信签名-可在短信控制台中找到
                    request.setSignName(aliSmsProperties.getSignName());
                    //必填:短信模板-可在短信控制台中找到
                    request.setTemplateCode(aliSmsProperties.getTemplate());
                    //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
                    request.setTemplateParam(mapToTemplateParas(msg));
                    //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
                    request.setOutId("无");
                    //hint 此处可能会抛出异常，注意catch
                    SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
                    if (!"OK".equals(sendSmsResponse.getCode())) {
                        log.info("[短信服务] 发送短信失败, phoneNumber:{}, 原因：{}", phoneNumber, sendSmsResponse.getMessage());
                    }
                    break;
                default:
            }
        } catch (Exception e) {
            log.error("[短信服务] 发送短信异常, 手机号码：{}", key, e);
        }
    }

    public static class AliSmsProperties extends SmsProperties {
        /**
         * 模板ID
         */
        private String template;

        public AliSmsProperties(ManageSmsProperties prop, String template) {
            super(prop);
            if (template == null || prop == null) {
                return;
            }
            switch (template) {
                case SmsConstants.Template.CODE:
                    this.template = prop.getVerifyCode().getTemplate();
                    break;
                case SmsConstants.Template.PAYMENT:
                    this.template = prop.getPaymentSms().getTemplate();
                    break;
                case SmsConstants.Template.REVIEW_PRES_RESULT:
                    this.template = prop.getReviewPresResult().getTemplate();
                    break;
                case SmsConstants.Template.UN_REVIEW_PRES:
                    this.template = prop.getUnReviewPres().getTemplate();
                    break;
                case SmsConstants.Template.AUTH_TYPE2_LAPSE:
                    this.template = prop.getAuthType2Lapse().getTemplate();
                    break;
                case SmsConstants.Template.DOCTOR_REGISTRY_SUC:
                    this.template = prop.getDoctorRegistrySuc().getTemplate();
                    break;
                case SmsConstants.Template.DOCTOR_BIND_RESULT:
                    this.template = prop.getReviewDoctorBindResult().getTemplate();
                    break;
                case SmsConstants.Template.UN_REVIEW_DOCTOR_BIND:
                    this.template = prop.getUnReviewDoctorBind().getTemplate();
                    break;
                case SmsConstants.Template.INTERFLOW_NOT_REPLY:
                    this.template = prop.getInterflowNotReply().getTemplate();
                    break;
                case SmsConstants.Template.APPLY_PASS:
                    this.template = prop.getApplyPass().getTemplate();
                    break;
                case SmsConstants.Template.APPLY_FAIL:
                    this.template = prop.getApplyFail().getTemplate();
                    break;
                case SmsConstants.Template.DELIVERY_PRES:
                    this.template = prop.getDeliveryPres().getTemplate();
                    break;
                case SmsConstants.Template.ZRK_INTERFACE_ERROR:
                    this.template = prop.getZrkInterfaceError().getTemplate();
                    break;
                case SmsConstants.Template.WIDTHDRAW_FINANCE_NEWS:
                    this.template = prop.getWithdrawFinanceNews().getTemplate();
                    break;
                case SmsConstants.Template.CS_ACCOUNT_EXPIRE:
                    this.template = prop.getCsAccountExpire().getTemplate();
                    break;
                case SmsConstants.Template.CS_ACCOUNT_NO_USE:
                    this.template = prop.getCsAccountNoUse().getTemplate();
                    break;
                case SmsConstants.Template.GOODS_EXCHANGE:
                    this.template = prop.getGoodsExchange().getTemplate();
                    break;
                case SmsConstants.Template.REGISTER_AUTH:
                    this.template = prop.getRegisterAuth().getTemplate();
                    break;
                default:
            }
        }

        public String getTemplate() {
            return template;
        }

        public void setTemplate(String template) {
            this.template = template;
        }
    }

    /**
     * 构造X-WSSE参数值
     *
     * @param appKey    APP_Key
     * @param appSecret APP_Secret
     * @return
     */
    public static String buildWsseHeader(String appKey, String appSecret) {
        if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
            System.out.println("buildWsseHeader(): appKey or appSecret is null.");
            return null;
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        String time = sdf.format(new Date());
        String nonce = UUID.randomUUID().toString().replace("-", "");
        MessageDigest md;
        byte[] passwordDigest = null;
        try {
            md = MessageDigest.getInstance("SHA-256");
            md.update((nonce + time + appSecret).getBytes());
            passwordDigest = md.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //如果JDK版本是1.8,请加载原生Base64类,并使用如下代码
        String passwordDigestBase64Str = Base64.getEncoder().encodeToString(passwordDigest);
        return String.format(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
    }

    public static class HuaweiSmsProperties extends SmsProperties {
        /**
         * 短信接入地址
         */
        private String url;
        /**
         * 发送短信通道号
         */
        private String sender;
        /**
         * 模板ID
         */
        private String template;

        public HuaweiSmsProperties(ManageSmsProperties prop, String template) {
            super(prop);
            if (template == null || prop == null) {
                return;
            }
            switch (template) {
                case SmsConstants.Template.CODE:
                    this.url = prop.getVerifyCode().getUrl();
                    this.sender = prop.getVerifyCode().getSender();
                    this.template = prop.getVerifyCode().getTemplate();
                    break;
                case SmsConstants.Template.PAYMENT:
                    this.url = prop.getPaymentSms().getUrl();
                    this.sender = prop.getPaymentSms().getSender();
                    this.template = prop.getPaymentSms().getTemplate();
                    break;
                case SmsConstants.Template.REVIEW_PRES_RESULT:
                    this.url = prop.getReviewPresResult().getUrl();
                    this.sender = prop.getReviewPresResult().getSender();
                    this.template = prop.getReviewPresResult().getTemplate();
                    break;
                case SmsConstants.Template.UN_REVIEW_PRES:
                    this.url = prop.getUnReviewPres().getUrl();
                    this.sender = prop.getUnReviewPres().getSender();
                    this.template = prop.getUnReviewPres().getTemplate();
                    break;
                case SmsConstants.Template.AUTH_TYPE2_LAPSE:
                    this.url = prop.getAuthType2Lapse().getUrl();
                    this.sender = prop.getAuthType2Lapse().getSender();
                    this.template = prop.getAuthType2Lapse().getTemplate();
                    break;
                case SmsConstants.Template.DOCTOR_REGISTRY_SUC:
                    this.url = prop.getDoctorRegistrySuc().getUrl();
                    this.sender = prop.getDoctorRegistrySuc().getSender();
                    this.template = prop.getDoctorRegistrySuc().getTemplate();
                    break;
                case SmsConstants.Template.DOCTOR_BIND_RESULT:
                    this.url = prop.getReviewDoctorBindResult().getUrl();
                    this.sender = prop.getReviewDoctorBindResult().getSender();
                    this.template = prop.getReviewDoctorBindResult().getTemplate();
                    break;
                case SmsConstants.Template.UN_REVIEW_DOCTOR_BIND:
                    this.url = prop.getUnReviewDoctorBind().getUrl();
                    this.sender = prop.getUnReviewDoctorBind().getSender();
                    this.template = prop.getUnReviewDoctorBind().getTemplate();
                    break;
                case SmsConstants.Template.INTERFLOW_NOT_REPLY:
                    this.url = prop.getInterflowNotReply().getUrl();
                    this.sender = prop.getInterflowNotReply().getSender();
                    this.template = prop.getInterflowNotReply().getTemplate();
                    break;
                case SmsConstants.Template.APPLY_PASS:
                    this.url = prop.getApplyPass().getUrl();
                    this.sender = prop.getApplyPass().getSender();
                    this.template = prop.getApplyPass().getTemplate();
                    break;
                case SmsConstants.Template.APPLY_FAIL:
                    this.url = prop.getApplyFail().getUrl();
                    this.sender = prop.getApplyFail().getSender();
                    this.template = prop.getApplyFail().getTemplate();
                    break;
                case SmsConstants.Template.DELIVERY_PRES:
                    this.url = prop.getApplyFail().getUrl();
                    this.sender = prop.getApplyFail().getSender();
                    this.template = prop.getDeliveryPres().getTemplate();
                    break;
                case SmsConstants.Template.ZRK_INTERFACE_ERROR:
                    this.url = prop.getZrkInterfaceError().getUrl();
                    this.sender = prop.getZrkInterfaceError().getSender();
                    this.template = prop.getZrkInterfaceError().getTemplate();
                    break;
                case SmsConstants.Template.WIDTHDRAW_FINANCE_NEWS:
                    this.url = prop.getWithdrawFinanceNews().getUrl();
                    this.sender = prop.getWithdrawFinanceNews().getSender();
                    this.template = prop.getWithdrawFinanceNews().getTemplate();
                    break;
                case SmsConstants.Template.CS_ACCOUNT_EXPIRE:
                    this.url = prop.getCsAccountExpire().getUrl();
                    this.sender = prop.getCsAccountExpire().getSender();
                    this.template = prop.getCsAccountExpire().getTemplate();
                    break;
                case SmsConstants.Template.CS_ACCOUNT_NO_USE:
                    this.url = prop.getCsAccountNoUse().getUrl();
                    this.sender = prop.getCsAccountNoUse().getSender();
                    this.template = prop.getCsAccountNoUse().getTemplate();
                    break;
                case SmsConstants.Template.GOODS_EXCHANGE:
                    this.url = prop.getGoodsExchange().getUrl();
                    this.sender = prop.getGoodsExchange().getSender();
                    this.template = prop.getGoodsExchange().getTemplate();
                    break;
                case SmsConstants.Template.REGISTER_AUTH:
                    this.url = prop.getRegisterAuth().getUrl();
                    this.sender = prop.getRegisterAuth().getSender();
                    this.template = prop.getRegisterAuth().getTemplate();
                    break;
                default:
            }
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getSender() {
            return sender;
        }

        public void setSender(String sender) {
            this.sender = sender;
        }

        public String getTemplate() {
            return template;
        }

        public void setTemplate(String template) {
            this.template = template;
        }
    }
}
